package org.cuit.seon.explore.aop;

import cn.hutool.core.util.StrUtil;
import cn.hutool.core.util.URLUtil;
import cn.hutool.json.JSONUtil;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.Signature;
import org.aspectj.lang.annotation.*;
import org.aspectj.lang.reflect.MethodSignature;
import org.cuit.seon.explore.log.LogRecord;
import org.springframework.core.Ordered;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

import javax.servlet.http.HttpServletRequest;
import java.lang.reflect.Method;
import java.lang.reflect.Parameter;
import java.time.Instant;
import java.time.temporal.ChronoUnit;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
 * @author wuwen
 * @since 2023年12月29日
 * <p>
 * 网络活动日志监控切面
 */
@Aspect
@Component
@Order(Ordered.HIGHEST_PRECEDENCE + 1)
@Slf4j
public class WebInteractionLogAspect {

    /*
    注意  ..*  这个符号被加在哪个包名后面，就意味着是这个包下面以及下面的所有子包
    定义切点表达式,指定通知功能被应用的范围
     */
    @Pointcut("execution(public * org.cuit.seon.explore.controller..*.*(..))")
    public void webInteractionLog() {
    }

    @Before("webInteractionLog()")
    public void doBefore(JoinPoint joinPoint) throws Throwable {
        // 可以在这里加上参数校验什么，登录认证什么的
    }

    /**
     * value切入点位置
     * returning 自定义的变量，标识目标方法的返回值,自定义变量名必须和通知方法的形参一样
     * 特点：在目标方法之后执行的,能够获取到目标方法的返回值，可以根据这个返回值做不同的处理
     */
    @AfterReturning(value = "webInteractionLog()", returning = "result")
    public void doAfterReturning(Object result) throws Throwable {
    }

    //通知包裹了目标方法，在目标方法调用之前和之后执行自定义的行为
    //ProceedingJoinPoint切入点可以获取切入点方法上的名字、参数、注解和对象
    @Around("webInteractionLog()")
    public Object doAround(ProceedingJoinPoint joinPoint) throws Throwable {
        Object proceed = null;
        // 开始记录时间
        Instant startTime = Instant.now();
        //获取当前请求对象
        ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
        HttpServletRequest request = attributes.getRequest();
        //记录请求信息
        Signature signature = joinPoint.getSignature();
        MethodSignature methodSignature = (MethodSignature) signature;
        Method method = methodSignature.getMethod();
        String urlStr = request.getRequestURL().toString();
        // 构造日志记录
        LogRecord logRecord = new LogRecord()
                .setIp(request.getRemoteAddr())
                .setMethodName(methodSignature.getName())
                .setRequestType(request.getMethod())
                .setUri(request.getRequestURI())
                .setUrl(request.getRequestURL().toString())
                .setParameter(getParameter(method, joinPoint.getArgs()))
                .setStartTime(startTime.toEpochMilli())
                .setBasePath(StrUtil.removeSuffix(urlStr, URLUtil.url(urlStr).getPath()));
        try {
            proceed = joinPoint.proceed();
            logRecord.setResult(proceed);
        } catch (Exception e) {
            logRecord.setResult("系统异常:" + e);
            // 继续抛出异常，防止全局异常捕获被阻拦
            throw e;
        } finally {
            Instant endTime = Instant.now();
            logRecord.setSpendTime(ChronoUnit.MILLIS.between(startTime, endTime));
            log.info(logRecord.toString());
        }
        return proceed;
    }

    /**
     * 根据方法和传入的参数获取请求参数
     */
    private Object getParameter(Method method, Object[] args) {
        List<Object> argList = new ArrayList<>();
        Parameter[] parameters = method.getParameters();
        for (int i = 0; i < parameters.length; i++) {
            //将RequestBody注解修饰的参数作为请求参数
            RequestBody requestBody = parameters[i].getAnnotation(RequestBody.class);
            if (requestBody != null) {
                argList.add(args[i]);
            }
            //将RequestParam注解修饰的参数作为请求参数
            RequestParam requestParam = parameters[i].getAnnotation(RequestParam.class);
            if (requestParam != null) {
                Map<String, Object> map = new HashMap<>();
                String key = parameters[i].getName();
                if (StringUtils.hasText(requestParam.value())) {
                    key = requestParam.value();
                }
                map.put(key, args[i]);
                argList.add(map);
            }
        }
        return switch (argList.size()) {
            case 0 -> null;
            case 1 -> argList.get(0);
            default -> argList;
        };
    }
}